import type { SqlCommenterContext, SqlCommenterPlugin } from '@prisma/sqlcommenter'

import { appendSqlComment, applySqlCommenters, buildSqlComment, formatSqlComment } from './sql-commenter'

describe('formatSqlComment', () => {
  test('returns empty string for empty object', () => {
    expect(formatSqlComment({})).toBe('')
  })

  test('formats single key-value pair', () => {
    expect(formatSqlComment({ key: 'value' })).toBe("/*key='value'*/")
  })

  test('sorts keys lexicographically', () => {
    expect(formatSqlComment({ z: '1', a: '2', m: '3' })).toBe("/*a='2',m='3',z='1'*/")
  })

  test('URL-encodes keys', () => {
    expect(formatSqlComment({ 'key with spaces': 'value' })).toBe("/*key%20with%20spaces='value'*/")
  })

  test('URL-encodes values', () => {
    expect(formatSqlComment({ key: 'value with spaces' })).toBe("/*key='value%20with%20spaces'*/")
  })

  test('escapes single quotes in values after URL encoding', () => {
    // encodeURIComponent("it's") = "it's" (apostrophe is not encoded)
    // Then we replace ' with \'
    expect(formatSqlComment({ key: "it's" })).toBe("/*key='it\\'s'*/")
  })

  test('handles special characters', () => {
    // = is encoded to %3D, comma is encoded to %2C
    expect(formatSqlComment({ 'key=value': 'a,b' })).toBe("/*key%3Dvalue='a%2Cb'*/")
  })

  test('handles traceparent format', () => {
    const result = formatSqlComment({
      traceparent: '00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01',
    })
    expect(result).toBe("/*traceparent='00-0af7651916cd43dd8448eb211c80319c-b9c7c989f97918e1-01'*/")
  })

  test('handles multiple realistic tags', () => {
    const result = formatSqlComment({
      traceparent: '00-abc-def-01',
      'prisma-query': 'eyJtb2RlbCI6IlVzZXIifQ==',
      application: 'my-app',
    })
    // Keys sorted: application, prisma-query, traceparent
    // == is encoded to %3D%3D
    expect(result).toBe(
      "/*application='my-app',prisma-query='eyJtb2RlbCI6IlVzZXIifQ%3D%3D',traceparent='00-abc-def-01'*/",
    )
  })

  test('handles empty string values', () => {
    expect(formatSqlComment({ key: '' })).toBe("/*key=''*/")
  })

  test('handles numeric-like string values', () => {
    expect(formatSqlComment({ version: '123' })).toBe("/*version='123'*/")
  })
})

describe('applySqlCommenters', () => {
  const mockSingleContext: SqlCommenterContext = {
    query: {
      type: 'single',
      modelName: 'User',
      action: 'findMany',
      query: { selection: { name: true } },
    },
  }

  const mockCompactedContext: SqlCommenterContext = {
    query: {
      type: 'compacted',
      modelName: 'User',
      action: 'findUnique',
      queries: [
        { arguments: { where: { id: 1 } }, selection: { $scalars: true } },
        { arguments: { where: { id: 2 } }, selection: { $scalars: true } },
      ],
    },
  }

  test('returns empty object with no plugins', () => {
    expect(applySqlCommenters([], mockSingleContext)).toEqual({})
  })

  test('calls plugin with context', () => {
    const plugin = jest.fn(() => ({ key: 'value' }))
    applySqlCommenters([plugin], mockSingleContext)
    expect(plugin).toHaveBeenCalledWith(mockSingleContext)
  })

  test('returns merged results from single plugin', () => {
    const plugin: SqlCommenterPlugin = () => ({ key: 'value' })
    expect(applySqlCommenters([plugin], mockSingleContext)).toEqual({ key: 'value' })
  })

  test('merges results from multiple plugins', () => {
    const plugin1: SqlCommenterPlugin = () => ({ a: '1' })
    const plugin2: SqlCommenterPlugin = () => ({ b: '2' })
    expect(applySqlCommenters([plugin1, plugin2], mockSingleContext)).toEqual({ a: '1', b: '2' })
  })

  test('later plugins override earlier ones for same key', () => {
    const plugin1: SqlCommenterPlugin = () => ({ key: 'first' })
    const plugin2: SqlCommenterPlugin = () => ({ key: 'second' })
    expect(applySqlCommenters([plugin1, plugin2], mockSingleContext)).toEqual({ key: 'second' })
  })

  test('handles plugin returning empty object', () => {
    const plugin: SqlCommenterPlugin = () => ({})
    expect(applySqlCommenters([plugin], mockSingleContext)).toEqual({})
  })

  test('filters out undefined values from plugin results', () => {
    const plugin: SqlCommenterPlugin = () => ({
      included: 'yes',
      excluded: undefined,
    })
    expect(applySqlCommenters([plugin], mockSingleContext)).toEqual({ included: 'yes' })
  })

  test('filters undefined values when merging multiple plugins', () => {
    const plugin1: SqlCommenterPlugin = () => ({ a: '1', b: undefined })
    const plugin2: SqlCommenterPlugin = () => ({ c: '3', d: undefined })
    expect(applySqlCommenters([plugin1, plugin2], mockSingleContext)).toEqual({ a: '1', c: '3' })
  })

  test("undefined value from later plugin doesn't remove key set by earlier plugin", () => {
    const plugin1: SqlCommenterPlugin = () => ({ key: 'value' })
    const plugin2: SqlCommenterPlugin = () => ({ key: undefined })
    expect(applySqlCommenters([plugin1, plugin2], mockSingleContext)).toEqual({ key: 'value' })
  })

  test('works with compacted query context', () => {
    const plugin: SqlCommenterPlugin = (ctx) => {
      const result: Record<string, string> = { batch: ctx.query.type === 'compacted' ? 'true' : 'false' }
      if (ctx.query.type === 'compacted') {
        result.count = String(ctx.query.queries.length)
      }
      return result
    }
    expect(applySqlCommenters([plugin], mockCompactedContext)).toEqual({ batch: 'true', count: '2' })
  })

  test('plugin can distinguish between single and compacted queries', () => {
    const plugin: SqlCommenterPlugin = (ctx) => {
      return { type: ctx.query.type }
    }
    expect(applySqlCommenters([plugin], mockSingleContext)).toEqual({ type: 'single' })
    expect(applySqlCommenters([plugin], mockCompactedContext)).toEqual({ type: 'compacted' })
  })
})

describe('buildSqlComment', () => {
  const mockContext: SqlCommenterContext = {
    query: {
      type: 'single',
      modelName: 'User',
      action: 'findMany',
      query: {},
    },
  }

  test('returns empty string with no plugins', () => {
    expect(buildSqlComment([], mockContext)).toBe('')
  })

  test('formats result from plugins', () => {
    const plugin: SqlCommenterPlugin = () => ({ key: 'value' })
    expect(buildSqlComment([plugin], mockContext)).toBe("/*key='value'*/")
  })

  test('returns empty string when plugins return empty objects', () => {
    const plugin: SqlCommenterPlugin = () => ({})
    expect(buildSqlComment([plugin], mockContext)).toBe('')
  })
})

describe('appendSqlComment', () => {
  test('returns original SQL if comment is empty', () => {
    const sql = 'SELECT * FROM "User"'
    expect(appendSqlComment(sql, '')).toBe(sql)
  })

  test('appends comment with space separator', () => {
    const sql = 'SELECT * FROM "User"'
    const comment = "/*key='value'*/"
    expect(appendSqlComment(sql, comment)).toBe('SELECT * FROM "User" /*key=\'value\'*/')
  })

  test('works with complex SQL', () => {
    const sql = 'SELECT "id", "name" FROM "User" WHERE "active" = true ORDER BY "name"'
    const comment = "/*app='test'*/"
    expect(appendSqlComment(sql, comment)).toBe(
      'SELECT "id", "name" FROM "User" WHERE "active" = true ORDER BY "name" /*app=\'test\'*/',
    )
  })
})
